﻿using System;
using System.Collections.Generic;
using Sencha.Serialization.Contracts;
using System.IO;

namespace Sencha.Serialization.Serializers
{
	public sealed class StreamSerializer : TypeSerializer<Stream>
	{
		public static TypeSerializer<Stream> Instance = new StreamSerializer(DataContractCache.DefaultContract.GetKnownType(typeof(Stream)));

		public StreamSerializer(TypeDescription streamType)
		{
			if (streamType == null)
				throw new ArgumentNullException("streamType");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			this.TypeDescription = streamType;

			if (!typeof(Stream).IsAssignableFrom(streamType.GetClrType()))
				throw new Exceptions.TypeContractViolation(this.GetType(), streamType, "be a Stream");
		}

		public override Stream Deserialize(IJsonReader reader)
		{
			if (reader == null)
				throw new ArgumentNullException("reader");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (reader.Token == JsonToken.Null)
				return null;

			var base64Str = Convert.ToString(reader.RawValue, reader.Context.Format);
			var bytes = Convert.FromBase64String(base64Str);
			return new MemoryStream(bytes);
		}
		public override void Serialize(IJsonWriter writer, Stream value)
		{
			if (writer == null)
				throw new ArgumentNullException("writer");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (value == null)
			{
				writer.WriteNull();
				return;
			}

			Stream stream = value as Stream;

			if (stream == null)
				throw new Exceptions.TypeContractViolation(this.GetType(), value.GetType(), "be a Stream");

			if (!stream.CanRead)
				throw new Exceptions.UnreadableStream("value");

			var bufferSize = 3 * 1024 * 1024;
			var buffer = new byte[bufferSize]; // 3kb buffer

			// if it's a small seakable stream
			if (stream.CanSeek && stream.Length < bufferSize)
			{
				// read it to buffer
				stream.Read(buffer, 0, buffer.Length);
				// convert to base64
				var base64Str = Convert.ToBase64String(buffer, 0, (int)stream.Length);
				// and write it
				writer.WriteString(base64Str);
				return;
			}
			// else white with chunks

			var readed = 0;
			writer.WriteJson("\"");

			// read chunks
			while ((readed = stream.Read(buffer, 0, buffer.Length)) > 0)
			{
				var base64Str = Convert.ToBase64String(buffer, 0, readed);
				writer.WriteJson(base64Str);
			}

			writer.WriteJson("\"");
		}

		public override string ToString()
		{
			return string.Format("stream");
		}
	}
}
